// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// 
//     http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Steelmill menus and common logic

#import "PuzzleCommon.h"
#import "PuzzleScene.h"
#if COCOSDENSHION
#import "SimpleAudioEngine.h"
#endif

#import "FontLabel/FontManager.h"
#import "FontLabel/FontLabelStringDrawing.h"

extern const char* predefinedgrid;

const char* tips[] = {
	"TIP: Press the \"Help?\" button at any time to learn how to play.",
	"TIP: Touch two hearts of the same color to make their respective beams melt away. (Note that this doesn't work if one of the beams is still falling in midair.)",
	"TIP: Cleared all your beams?  Swipe upward with two fingers to raise the stack.",
	"TIP: You can place beams anywhere, even under other beams.",
	"Keep at it!  The all-American manufacturing of Twin Hills Steel Mill can only survive thanks to your hard work!",
	"TIP: If you get hit by a scrap metal block, dispose of it by melting a colored beam that's touching it.",
	"TIP: S.T.E.E.L.bot increases speed when you beat him. Defeat his 8th speed to unlock him as a character!",
	"TIP: Be careful to avoid clutter so that your hearts are always reachable.",
	"Twin Hills Steel Mill: we work hard. We play hard.",
	"TIP: Make 'combos' by matching more than 2 hearts at a time.",
	"TIP: Make 'chains' by causing a self-sustaining sequence of matches.",
	"TIP: In single player, combos and chains give you extra vacation time. In versus mode, they cause scrap metal to fall on your opponent.",
	"TIP: You can cause a moderate amount of scrap to fall on your opponent simply by clearing beams quickly, but chaining is where the real power is.",
	"Twin Hills Steel Mill: we make strong steel products. We turn into big strong men.",
	"TIP: In two-player mode, use handicaps to have a fair match with a friend who's never played before. Handicaps affect game speed and scrap metal amounts.",
	"Twin Hills Steel Mill was established in 1879 by industrialist J. Hedwick Carnegie Melon Jr III.",
	"TIP: You can look at the background color to see what the next beam is without moving your eyes away from your workspace.",
	"Carnegie Melon crushed the 1901 steel strikes by using local bears as nonunionized labor, beginning a tradition that continues to this day.",
	"TIP: You can extend a chain by quickly placing more beams even while it's happening!",
	"TIP: To ensure a fair match, both players always start with the same randomly generated stack and the same sequence of beam colors.",
	"Twin Hills Steel Mill: our steel has heart!",
	"TIP: Scrap metal attacks are held briefly in the 'icon bar' on top of the screen before they drop. If produced by a chain, they will drop all at once when it terminates.",
	"Keith joined Twin Hills Steel Mill right out of high school and has been here ever since. Steel is his great love in life.",
	"TIP: You can get a sneak peek at your future stack by swiping up with two fingers, then back down without letting go.",
	"S.T.E.E.L.bot was created by utopian inventor Jay Kurzbile to improve the human condition. But he turned out to be a soulless, job-stealing machine.",
	"TIP: When up against a strong opponent, keep in mind that you can place your beams above the pile of scrap. It's often the best way to clean it out.",
	"In a historic union victory, the 1990 worker's contract guaranteed 3 months of vacation for all. However, the fine print said it would halve every year thereafter.",
	"TIP: The danger countdown is strict but fair. You will not die if you're in the middle of matching a beam -- provided it's actually supporting the beam that touches the top.",
	"TIP: You can place a beam on top of one that's in the middle of its disappearing animation. This is called a 'cancel' and is useful for chaining.",
};

const char* modetips[] = {
	"Puzzle mode: complete a steel obstacle course!",
	"Endless work mode: try to make it as far as possible as the stack rises faster!",
	"Pit your skills against the fearsome S.T.E.E.L. bot: make combos and chains to drop rubbish on him.",
	"Play against someone else on the same device!  This side-by-side view is good when you're on an airplane.",
	"Play against someone else on the same device!  This symmetrical view is good for facing off across a small table.",
};


//*******************************************************************************************************

int myrandom(unsigned int range) {
	if(range == 1) return 0;
	// Take high bits of range because low bits are very nonrandom (lowest bit is mostly 1)
	return rand() / (RAND_MAX / range + 1);
//#define ARC4RAND_MAX ((unsigned int) 0xFFFFFFFF)
//	return ((unsigned int) arc4random()) / (ARC4RAND_MAX / range + 1);
}

void ClearGridMutable(GridConfig* g) {
	g->wins = 0;
	g->is_ai = false;
	g->ailevel = 0;
#if STEELBOT6
	g->ailevel = 6;
#endif
	g->mascot = (Mascot)-1;
	g->difficulty = (Difficulty) -1;
	g->is_ready = false;
	g->practice = false;
	g->puzzle = false;
}

unsigned int emptydata;
CCTexture2D* emptytex;
CCTexture2D* blocktex[3][3];
CCAnimation* heartanims[3][3];
SceneManager* scene;
const char* charnames[3];

@implementation SceneManager

@synthesize state_;
@synthesize layout_;

+(id) sceneManager {
	return [[[SceneManager alloc] init] autorelease];
}

-(void) initGridConstants {
    int sidebarxsize, sidebarcenterx;
	GridConfig* g;
    g = &singleplayergrid;
	g->tilesize_x = g->tilesize_y = 57;
	g->numtiles_x = 8; g->numtiles_y = 17;
	g->playerno = 0;
	g->playerareasize_x = 768; g->playerareasize_y = 1024;
	g->playerareastart_x = 0;
	g->sidebar_x = 0;
	// sidebar xsize: 312, ysize: 1024
	sidebarxsize = 312;
	sidebarcenterx = sidebarxsize / 2;
	g->px_left = sidebarxsize;
	g->px_bottom = 0;
	g->nextlabelpos = ccp(sidebarcenterx, 200);
    g->dangersignpos = ccp(sidebarcenterx, 1024 - 275);
	g->upsidedown = false;
	g->mirrored = true;
	g->timerpos = ccp(41-10, 1024 - 127);
	g->pausepos = ccp(41-10, 1024 - 50);
	g->helppos = ccp(131-10, 1024 - 50);
	g->menupos = ccp(223-10, 1024 - 50);

    g->winlabelpos = ccp((GRID_XSIZE*g->tilesize_x)/2, 1024 - 300);
    g->winstartpos = ccp(1000, g->winlabelpos.y);
    g->mascotpos = ccp(sidebarcenterx + 25, 100);

	g = &base2playergrid;
	int center = 1024 / 2;
	g->tilesize_x = g->tilesize_y = 42;
	g->numtiles_x = 8; g->numtiles_y = 17;
	g->playerno = 0;
	g->playerareasize_x = 512; g->playerareasize_y = 768;
	g->playerareastart_x = 0;
	g->px_left = g->px_bottom = 0;
	g->sidebar_x = 0;
	// sidebar xsize: 176, ysize: 768
	sidebarxsize = 176;
	sidebarcenterx = sidebarxsize / 2;
	g->nextlabelpos = ccp(sidebarcenterx, 200);
	g->dangersignpos = ccp(sidebarcenterx, 768 - 250);
	g->mascotpos = ccp(sidebarcenterx, 50);
	g->timerpos = ccp(41, 768 - 127);
	g->pausepos = ccp(41, 768 - 50);
	g->helppos = ccp(131, 768 - 50);
	g->menupos = ccp(223, 768 - 50);

	g->winlabelpos = ccp((GRID_XSIZE*g->tilesize_x)/2, 768 - 300);
	g->winstartpos = ccp(800, g->winlabelpos.y);
	g->upsidedown = g->mirrored = false;

	{ // PARALLEL
		player1grid = player2grid = base2playergrid;
		player1grid.sidebar_x = 512 - sidebarxsize;
		player1grid.winstartpos.x = -150;

		player2grid.playerno = 1;
		player2grid.px_left += center + sidebarxsize;
		player2grid.playerareastart_x = 512;
		player2grid.sidebar_x = 512;
		player2grid.mirrored = true;
		player2grid.winstartpos.x = 1024 + 150;
		player2grid.timerpos = ccp(-1, -1);
		player2grid.upsidedown = false;
	}


	{ // REVERSED
		player1grid_reversed = base2playergrid;
		player1grid_reversed.mirrored = true;
		player1grid_reversed.px_left = sidebarxsize;
		player1grid_reversed.winlabelpos = ccp((GRID_XSIZE*g->tilesize_x)/2, 768 - 300);
		player1grid_reversed.pausepos = ccp(7, 722);
		player1grid_reversed.menupos = ccp(7 + 38, 722);
		player1grid_reversed.helppos = ccp(7 + 96, 722);
		player1grid_reversed.timerpos = ccp(7, 680);

		player2grid_reversed = player1grid_reversed;
		player2grid_reversed.playerno = 1;
		player2grid_reversed.upsidedown = true;

		player1grid_reversed.offsetx = center;
		player2grid_reversed.offsetx = -center;
	}
}

-(void) initDefaults {
	NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
	NSDate *lastRead  = (NSDate *)[defaults objectForKey:D_LASTREAD];
	if (lastRead == nil || CLEARDEFAULTS)  // first run
	{
		NSDictionary *appDefaults  = [NSDictionary dictionaryWithObjectsAndKeys:[NSDate date], D_LASTREAD, nil];

		[defaults setBool:false forKey:D_ROBOTUNLOCKED];
		[defaults setInteger:0 forKey:D_TIPCOUNT];
		[defaults setInteger:0 forKey:D_HEARTSCLEARED];
		[defaults setInteger:0 forKey:D_SPECIALCLEARED];
		[defaults setInteger:0 forKey:D_NUMPLAYS];
		[defaults setInteger:0 forKey:D_LINESMANUALLYRAISED];
		int robotmax = ROBOTMAX2 ? 2 : 8;
		[defaults setInteger:robotmax forKey:D_ROBOTMAXSPEED];
		int deepestreached = REACHED500 ? 500 : 0;
		[defaults setInteger:deepestreached forKey:D_DEEPESTREACHED];
		// TODO: clear num_chain/num_combo
		
		[defaults registerDefaults:appDefaults];
		[defaults synchronize];
	}
	[defaults setObject:[NSDate date] forKey:D_LASTREAD];
	int numplays = [defaults integerForKey:D_NUMPLAYS];
	[defaults setInteger:numplays+1 forKey:D_NUMPLAYS];

#if ROBOTUNLOCKED
	[defaults setBool:true forKey:D_ROBOTUNLOCKED];
#endif // #if ROBOTUNLOCKED
}

-(void) initChatDict {
	chatdict_ = [NSDictionary dictionaryWithObjectsAndKeys:
		@"Follow the gesture to place my block.  Tap on the \"Help?\" button to learn more.", C_PLACEHINT,
		@"Drag up with two fingers to get more blocks.", C_RAISEHINT,
		@"S.T.E.E.L.bot wants to \"steel\" your job!", C_TTBEGIN(ROBOT,KEITH,0),
		@"S.T.E.E.L.bot is superior to beasts.", C_TTBEGIN(ROBOT,BEAR,0),
		@"My servomotors are better oiled!", C_TTBEGIN(ROBOT,ROBOT,0),
		@"My electro- magnets have greater current!", C_TTBEGIN(ROBOT,ROBOT,1),
		@"I'll show you how a man does it.", C_TTBEGIN(KEITH,ROBOT,0),
		@"So you beat me at wrestling, but now we'll see your real steel.", C_TTBEGIN(KEITH,BEAR,0),
		@"Hey, I like your jeans.", C_TTBEGIN(KEITH,KEITH,0),
		@"Yours too, where did you buy them?", C_TTBEGIN(KEITH,KEITH,1),
		@"Growrorrrrr!", C_TTBEGIN(BEAR,ROBOT,0),
		@"Roaaaargggrrr!", C_TTBEGIN(BEAR,KEITH,0),
		@"Groar?", C_TTBEGIN(BEAR,BEAR,0),
		@"Gro growwr roar!", C_TTBEGIN(BEAR,BEAR,1),
		nil];
	[chatdict_ retain];
}

-(id) init {
	if(!(self = [super init])) return nil;

#if !COCOSDENSHION
	sounddict_ = [[NSMutableDictionary alloc] init];
#endif

	[self initGridConstants];
	[self initDefaults];
	[self initChatDict];

	// Init statics
	emptydata = 0;
	emptytex = [[CCTexture2D alloc] initWithData:&emptydata
		                           pixelFormat:kTexture2DPixelFormat_RGBA8888
								   pixelsWide:1
								   pixelsHigh:1
								   contentSize:CGSizeMake(1,1)];
	charnames[0] = "man";
	charnames[1] = "bear";
	charnames[2] = "robot";
	for(int c = 0; c < 3; c++) {
		for(int j = 0; j < 3; j++) {
			blocktex[c][j] = [[[CCTextureCache sharedTextureCache] addImage:[NSString stringWithFormat:@"Assets/Graphics/block-%s%d.png", charnames[c], j]] retain];
			heartanims[c][j] = [[CCAnimation animationWithName:@"heart" delay:(1.0/12)] retain];
			for(int i = 1; i <= 5; i++) {
				[heartanims[c][j] addFrameWithFilename:[NSString stringWithFormat:@"Assets/Graphics/hearts/%s%d/heart-%04d.png", charnames[c], j, i]];
			}
		}
	}
	scene = self;

	// Preload these large textures for speed
	[[CCTextureCache sharedTextureCache] addImage:@"Assets/Graphics/center-area-left.png"];
	[[CCTextureCache sharedTextureCache] addImage:@"Assets/Graphics/center-area-right.png"];
	[[CCTextureCache sharedTextureCache] addImage:@"Assets/Graphics/left-area-1p.png"];

	bufferedPortraitOrientation_ = kCCDeviceOrientationPortrait;
	bufferedLandscapeOrientation_ = kCCDeviceOrientationLandscapeRight;

	state_ = (GameState) -1;
	layout_ = (GameLayout) -1;

#if BGSQUARES
	bg_ = [CCNode node];
	[self addChild:bg_];
	bool alt = false;
	for(int y = 0; y < 13; y++) {
		for(int x = 0; x < 13; x++) {
			CCSprite* sq = [[[CCSprite alloc] init] autorelease];
			[sq setTextureRect:CGRectMake(0, 0, BGSQUAREX, BGSQUAREY)];
			[bg_ addChild:sq];
			sq.position = ccp((x-1) * BGSQUAREX, (y-1) * BGSQUAREY);
			[sq setColor:alt ? ccc3(0,0,0) : ccc3(0x20, 0x20, 0x20)];
			alt = !alt;
		}
	}
	[self schedule: @selector(scrollBackground:) interval:FRAMETIME];

	[bg_ runAction: [CCSequence actions:
			[CCOrbitCamera actionWithDuration:CARDFLIPTIME radius:1 deltaRadius:0 angleZ:20 deltaAngleZ:0 angleX:10 deltaAngleX:0],
		nil]];
#endif
	screendim_ = [[[CCSprite alloc] init] autorelease];
	[screendim_ setTextureRect:CGRectMake(0, 0, 1025, 1025)];
	[screendim_ setColor: ccc3(0,0,0)];
	[screendim_ setOpacity: 0xB0];
	screendim_.visible = false;
	screendim_.position = ccp(1024/2, 1024/2);
	[self addChild:screendim_ z:49];

	menubox_ = [CCNode node];
	[self addChild:menubox_ z:50];

	paused_ = [CCLabel labelWithString:@"PAUSED" fontName:@"Franchise" fontSize:120];
	paused_.visible = false;
	[menubox_ addChild:paused_];
//	returntomenu_ = [CCSprite spriteWithFile:@"Assets/Graphics/returntomainmenu.png"];
	menuquestion_ = [CCLabel labelWithString:@"Are you sure you wish to return to the main menu?" dimensions:CGSizeMake(600, 300) alignment:UITextAlignmentCenter fontName:@"BPreplay" fontSize:40];
	menuquestion_.visible = false;
	menuquestion_.position = ccp(0, 100);
	[menubox_ addChild:menuquestion_];
	menuyes_ = [CCLabel labelWithString:@"Yes" fontName:@"BPreplayBold" fontSize:60];
	menuyes_.visible = false;
	menuyes_.position = ccp(-100, -75);
	[menubox_ addChild:menuyes_];
	menuno_ = [CCLabel labelWithString:@"No" fontName:@"BPreplayBold" fontSize:60];
	menuno_.visible = false;
	menuno_.position = ccp(100, -75);
	[menubox_ addChild:menuno_];

	[[CCTouchDispatcher sharedDispatcher] addTargetedDelegate:self priority:-1 swallowsTouches:YES];

	return self;
}

- (BOOL)ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event {
	if(scene.state_ == INSTRUCTIONS) return false;
	if(ispaused_) {
		if(ismenudialog_) {
			CGSize butsize = [menuyes_ contentSize];
			CGRect butrect = CGRectMake(0,0,butsize.width,butsize.height);
			if(CGRectContainsPoint(butrect, [menuyes_ convertTouchToNodeSpace:touch])) {
				[self changeState:MAINMENU];
				[self endPause];
			}

			butsize = [menuno_ contentSize];
			butrect = CGRectMake(0,0,butsize.width,butsize.height);
			if(CGRectContainsPoint(butrect, [menuno_ convertTouchToNodeSpace:touch])) {
				[self endPause];
			}

			return true;
		} else {
			[self endPause];
			return true;
		}
	}

	if(state_ == ENDED_TWOPLAYER) {
		[scene changeLayout:scene->layout_];
		[scene changeState:PLAYING];
		return true;
	}

	if(state_ == ENDED_SOLO) {
		[layer1_ checkStatsButtons:touch];
		[layer1_ checkSideButtons:touch];
		return true;
	}

	if(state_ == ENDED_VSAI_COMPLETE) {
		// TODO: only restart when button provided
		[scene changeState:MAINMENU];
		return true;
	}

	return false;
}

-(void) beginPause:(bool)menu isRotated:(bool)isrotated {
	ispaused_ = true;
	screendim_.visible = true;

	if(!menu) {
		paused_.visible = true;
	} else {
		menuquestion_.visible = true;
		menuyes_.visible = true;
		menuno_.visible = true;
		ismenudialog_ = true;
	}

	menubox_.rotation = isrotated ? 180 : 0;

	[layer1_ pause];
	[layer2_ pause];
	[[CCDirector sharedDirector] pause];
}

-(void) endPause {
	ispaused_ = false;
	ismenudialog_ = false;
	paused_.visible = false;
	menuquestion_.visible = false;
	menuyes_.visible = false;
	menuno_.visible = false;
	screendim_.visible = false;
	[layer1_ unpause];
	[layer2_ unpause];
	[[CCDirector sharedDirector] resume];
}


-(void) cleanPlayfield {
	// Release old layers if any
	if(layer1_ != nil) {
		[[CCTouchDispatcher sharedDispatcher] removeDelegate:layer1_];
		[self removeChild:layer1_ cleanup:true];
		layer1_ = nil;
	}
	if(layer2_ != nil) {
		[[CCTouchDispatcher sharedDispatcher] removeDelegate:layer2_];
		[self removeChild:layer2_ cleanup:true];
		layer2_ = nil;
	}
}

-(void) changeLayout:(GameLayout)layout {
	layout_ = layout;

	[self cleanPlayfield];

	PuzzleCommon* common;
	switch(layout) {
		case SINGLEPLAYER:
			[self forceRotation:bufferedPortraitOrientation_];
			menubox_.position = ccp(768/2, 1024/2);
			if(singleplayergrid.mascot == -1) singleplayergrid.mascot = KEITH;
			common = [[[PuzzleCommon alloc] initWithGridConfig:&singleplayergrid] autorelease];
			layer1_ = [[[PuzzleStack alloc] initWithGridConfig:&singleplayergrid common:common] autorelease];

			[self addChild: layer1_ z:1];
			break;
		case SINGLEVSAI:
		case TWOPLAYERS:
		case TWOPLAYERSREVERSED:
			if(state_ != ENDED_TWOPLAYER || currentOrientation_ == kCCDeviceOrientationPortrait || currentOrientation_ == kCCDeviceOrientationPortraitUpsideDown) {
				[self forceRotation:bufferedLandscapeOrientation_];
			}
			menubox_.position = ccp(1024/2, 768/2);
			if(base2playergrid.mascot == -1) base2playergrid.mascot = KEITH;
			common = [[[PuzzleCommon alloc] initWithGridConfig:&base2playergrid] autorelease];
			layer1_ = [PuzzleStack alloc];
			layer2_ = [PuzzleStack alloc];
			layer1_.opponent_ = layer2_;
			layer2_.opponent_ = layer1_;
			if(player1grid.mascot == -1) player1grid.mascot = KEITH;
			if(layout == TWOPLAYERS || layout == SINGLEVSAI) {
				if(layout == SINGLEVSAI) {
					player1grid.is_ai = true;
					if(player2grid.mascot == -1) player2grid.mascot = KEITH;
				} else {
					if(player2grid.mascot == -1) player2grid.mascot = BEAR;
				}
				layer2_.g_ = &player2grid;
				layer1_ = [[layer1_ initWithGridConfig:&player1grid common:common] autorelease];
				layer2_ = [[layer2_ initWithGridConfig:&player2grid common:common] autorelease];
			} else {
				if(player1grid_reversed.mascot == -1) player1grid_reversed.mascot = KEITH;
				if(player2grid_reversed.mascot == -1) player2grid_reversed.mascot = BEAR;
				layer2_.g_ = &player2grid_reversed;
				layer1_ = [[layer1_ initWithGridConfig:&player1grid_reversed common:common] autorelease];
				layer2_ = [[layer2_ initWithGridConfig:&player2grid_reversed common:common] autorelease];
			}
			[self addChild: layer1_ z:2];
			[self addChild: layer2_ z:1];

			break;
		default:
			assert(false);
			break;
	}
}

-(void) changeState:(GameState)state {
	GameState oldstate = state_;
	state_ = state;

	ccTime delay;
	bool showcredits = false;
	switch(state) {
		case MAINMENU:
			[self unschedule: @selector(endTimer:)];
			[self removeChild:instructions_ cleanup:true];
			instructions_ = nil;
			[self cleanPlayfield];
			[self forceRotation:bufferedLandscapeOrientation_];
			ClearGridMutable(&singleplayergrid);
			ClearGridMutable(&base2playergrid);
			ClearGridMutable(&player1grid);
			ClearGridMutable(&player2grid);
			ClearGridMutable(&player1grid_reversed);
			ClearGridMutable(&player2grid_reversed);

			// React to memory warning when reentering main menu since that's when we
			// can safely clear almost everything with no bugs or noticeable lag.
			if(memorywarning_ || FORCEMEMORYWARNING) {
				memorywarning_ = false;
#if COCOSDENSHION
				// clean out sounds
				[SimpleAudioEngine end];
				mainmenumusic_ = false;
#endif
				// clean out textures
				[[CCTextureCache sharedTextureCache] removeUnusedTextures];
			}

			[scene playMusic:@"Assets/Music/Fralke.m4a"];
			playinginterstitial_ = true;

			mainmenu_ = [[[MainMenu alloc] init] autorelease];
			[self addChild: mainmenu_];
			break;
		case CREDITS:
			showcredits = true;
		case INSTRUCTIONS:
			[self removeChild:mainmenu_ cleanup:true];
			mainmenu_ = nil;
			[self forceRotation:bufferedLandscapeOrientation_];
			instructions_ = [[[Instructions alloc] init:oldstate showCredits:showcredits] autorelease];
			[self addChild: instructions_ z:3];
			if(oldstate == PLAYING) {
				[layer1_ pause];
				[layer2_ pause];
			}
			break;
		case PLAYING:
			[self removeChild:mainmenu_ cleanup:true];
			mainmenu_ = nil;
			[self removeChild:instructions_ cleanup:true];
			instructions_ = nil;

			if(oldstate == INSTRUCTIONS) {
				if(layout_ == SINGLEPLAYER) {
					[self forceRotation:bufferedPortraitOrientation_];
				}
				[layer1_ unpause];
				[layer2_ unpause];
			}
			break;
		case ENDING:
			delay = ENDDELAY;
			if(layout_ == SINGLEPLAYER) delay = 1.0;
			[self schedule: @selector(endTimer:) interval:delay];
			break;
		case ENDED_SOLO:
			[self forceRotation:bufferedPortraitOrientation_];
			[self removeChild:instructions_ cleanup:true];
			instructions_ = nil;
			[layer1_ createStatsView];
			break;
		case ENDED_VSAI:
			if(layer1_.robotmaxed_ && layer2_.won_) {
				[layer2_ terminateWin];
				[layer1_.winlabel_ runAction:[CCFadeOut actionWithDuration:0.3]];
				[layer2_.winlabel_ runAction:[CCFadeOut actionWithDuration:0.3]];
				[layer1_ destroyRobotAnimation];
			} else {
				// Immediately restart
				[scene changeLayout:scene->layout_];
				[scene changeState:PLAYING];
				return;
			}
			break;
		case ENDED_VSAI_COMPLETE:
			break;
		case ENDED_TWOPLAYER:
			[layer1_ terminateWin];
			[layer2_ terminateWin];
			[layer1_ showWins];
			[layer2_ showWins];
			break;

		default:
			assert(false);
			break;
	}
}

- (void) scrollBackground: (ccTime) dt {
	id squares = [bg_ children];

	float moveby = SCROLLSPEED * dt * BGSQUAREX;
	CGPoint p1 = [((CCNode*)[squares objectAtIndex:0]) position];
	if(p1.x + moveby > 0) moveby -= BGSQUAREX;

	CCSprite* sq;
	for(sq in squares) {
		CGPoint pos = sq.position;
		pos.x += moveby;
		pos.y += moveby;
		sq.position = pos;
	}
}

- (void) endTimer: (ccTime) dt {
	[self unschedule: @selector(endTimer:)];
	// reset game state
	switch(layout_) {
		case SINGLEPLAYER:
			[scene changeState:ENDED_SOLO];
			break;
		case SINGLEVSAI:
			[scene changeState:ENDED_VSAI];
			break;
		case TWOPLAYERS:
		case TWOPLAYERSREVERSED:
			[scene changeState:ENDED_TWOPLAYER];
			break;
	}
}

-(void) receiveUserOrientation:(ccDeviceOrientation)interfaceOrientation {
	bool portraitscene = false;
	if(scene.state_ != MAINMENU && scene.state_ != INSTRUCTIONS && scene.state_ != CREDITS && scene.layout_ == SINGLEPLAYER) {
		portraitscene = true;
	}

	// Never rotate the opposide-sides view as it's already symmetrical
	bool reversed = (scene.layout_ == TWOPLAYERSREVERSED && scene.state_ != MAINMENU && scene.state_ != INSTRUCTIONS && scene.state_ != CREDITS);

	ccDeviceOrientation newOrientation = currentOrientation_;

	switch(interfaceOrientation) {
		case kCCDeviceOrientationPortrait:
		case kCCDeviceOrientationPortraitUpsideDown:
			scene->bufferedPortraitOrientation_ = interfaceOrientation;
			if(portraitscene) newOrientation = interfaceOrientation;
			break;
		case kCCDeviceOrientationLandscapeRight:
		case kCCDeviceOrientationLandscapeLeft:
			scene->bufferedLandscapeOrientation_ = interfaceOrientation;
			if(!portraitscene && !reversed) {
				newOrientation = interfaceOrientation;
			}
			break;
	}

	[self forceRotation:newOrientation];
}

-(void) forceRotation:(ccDeviceOrientation)newOrientation {
	if(newOrientation != currentOrientation_) {
		currentOrientation_ = newOrientation;

#if AUTOROTATEANIMATION
		CGPoint lrpos = ccp(-130, -128);
		int lrrot = -90;
		if(!everrotated_) {
			everrotated_ = true;

//			assert(newOrientation == kCCDeviceOrientationLandscapeRight);

			self.position = lrpos;
			self.rotation = lrrot;
			return;
		}

		float delay = 0.25f;
		if(newOrientation == kCCDeviceOrientationLandscapeLeft)
		{
			[self runAction:[CCMoveTo actionWithDuration: delay position:ccp(130, 128)]];
			[self runAction:[CCRotateTo actionWithDuration:delay angle: 90]];
		}
		else if(newOrientation == kCCDeviceOrientationLandscapeRight)
		{
			[self runAction:[CCMoveTo actionWithDuration: delay position:lrpos]];
			[self runAction:[CCRotateTo actionWithDuration:delay angle:lrrot]];
		}
		else if(newOrientation == kCCDeviceOrientationPortrait)
		{
			[self runAction:[CCMoveTo actionWithDuration: delay position:ccp(0, 0)]];
			[self runAction:[CCRotateTo actionWithDuration:delay angle: 0]];
		} else if(newOrientation == kCCDeviceOrientationPortraitUpsideDown)
		{
			[self runAction:[CCMoveTo actionWithDuration: delay position:ccp(0, 0)]];
			[self runAction:[CCRotateTo actionWithDuration:delay angle: -180]];
		} 
#else
		[[CCDirector sharedDirector] setDeviceOrientation:newOrientation];
#endif  // #if AUTOROTATEANIMATION
	}
}

-(void) playMusic:(NSString*) music {
	if([music isEqualToString:@"Assets/Music/Fralke.m4a"]) {
		if(mainmenumusic_) return;
		mainmenumusic_ = true;
	} else {
		mainmenumusic_ = false;
	}
#if COCOSDENSHION
	[[SimpleAudioEngine sharedEngine] playBackgroundMusic:music loop:true];
#else
	if(music_) [music_ release];
	NSString* path = [NSString stringWithFormat:@"%@/%@",
							  [[NSBundle mainBundle] resourcePath],
												  music];
		
	music_ = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:path isDirectory:false] error:nil];
	music_.numberOfLoops = -1;
	[music_ play];
#endif
}

-(void) pauseMusic {
#if COCOSDENSHION
	[[SimpleAudioEngine sharedEngine] pauseBackgroundMusic];
#else
	[music_ pause];
#endif
}
-(void) resumeMusic {
#if COCOSDENSHION
	[[SimpleAudioEngine sharedEngine] resumeBackgroundMusic];
#else
	[music_ play];
#endif
}

-(int) playSound:(NSString*) sound volume:(float)volume {
#if COCOSDENSHION
	return [[SimpleAudioEngine sharedEngine] playEffect:sound pitch:1.0 pan:1.0 gain:volume];
#else
	AVAudioPlayer* a = [sounddict_ objectForKey:sound];
	if(a == nil) {
		NSString* path = [NSString stringWithFormat:@"%@/%@",
								  [[NSBundle mainBundle] resourcePath],
													  sound];
		a = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:path isDirectory:false] error:nil];
		a.volume = volume;
		[sounddict_ setObject:a forKey:sound];
	}
	[a play];
	return -1;

	/*
	int i;
	// Wipe any old finished sound objects
	for(i = 0; i < MAX_SOUNDS; i++) {
		if(sounds_[i] && !sounds_[i].playing) {
			[sounds_[i] release];
			sounds_[i] = nil;
		}
	}
	// Allocate the sound to the first available slot
	for(i = 0; i < MAX_SOUNDS; i++) {
		if(sounds_[i] == nil) {
			NSString* path = [NSString stringWithFormat:@"%@/%@",
									  [[NSBundle mainBundle] resourcePath],
														  sound];
				
			sounds_[i] = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:path isDirectory:false] error:nil];
			sounds_[i].volume = volume;
			[sounds_[i] play];
			return i;
		}
	}
	// Just don't play it if MAX_SOUNDS are already playing
	return -1; */
#endif
}

-(void) stopSound:(int) soundid {
#if COCOSDENSHION
	[[SimpleAudioEngine sharedEngine] stopEffect:soundid];
#else
	/*
	if(soundid == -1) return;
	if(sounds_[soundid] == nil || !sounds_[soundid].playing) return;
	[sounds_[soundid] stop];
	*/
#endif
}

@end

@implementation MainMenu

-(id) init {
	if(!(self = [super init])) return nil;

	self.isTouchEnabled = true;

	menubg_[0] = [CCSprite spriteWithFile:@"Assets/Graphics/menu/menu-bg-left-top.png"];
	menubg_[0].anchorPoint = ccp(0, 1);
	menubg_[0].position = ccp(0, 768);
	[self addChild:menubg_[0] z:MENUBGLEFTTOP_Z];
	menubg_[1] = [CCSprite spriteWithFile:@"Assets/Graphics/menu/menu-bg-left-bottom.png"];
	menubg_[1].anchorPoint = ccp(0, 0);
	[self addChild:menubg_[1] z:MENUBGLEFTBOTTOM_Z];
	menubg_[2] = [CCSprite spriteWithFile:@"Assets/Graphics/menu/menu-bg-right.png"];
	menubg_[2].anchorPoint = ccp(1, 0);
	menubg_[2].position = ccp(1023, 0);
	[self addChild:menubg_[2] z:MENUBGRIGHT_Z];
	menubg_[3] = [CCSprite spriteWithFile:@"Assets/Graphics/menu/menu-bg-middle.png"];
	menubg_[3].anchorPoint = ccp(0, 0);
	menubg_[3].position = ccp(690, 0);
	[self addChild:menubg_[3] z:MENUBGMIDDLE_Z];

	single_ = [CCSprite spriteWithFile:@"Assets/Graphics/menu/btn-1p-endless.png"];
	singlevsai_ = [CCSprite spriteWithFile:@"Assets/Graphics/menu/btn-1p-steelbot.png"];
	twoplayer_ = [CCSprite spriteWithFile:@"Assets/Graphics/menu/btn-2p-sidebyside.png"];
	twoplayerreversed_ = [CCSprite spriteWithFile:@"Assets/Graphics/menu/btn-2p-acrosstable.png"];
	help_ = [CCSprite spriteWithFile:@"Assets/Graphics/menu/btn-help.png"];
	credits_ = [CCSprite spriteWithFile:@"Assets/Graphics/menu/btn-credits.png"];
	tiparrows_[0] = [CCSprite spriteWithFile:@"Assets/Graphics/menu/tip-arrow-left.png"];
	tiparrows_[1] = [CCSprite spriteWithFile:@"Assets/Graphics/menu/tip-arrow-right.png"];

	currenttip_ = [[NSUserDefaults standardUserDefaults] integerForKey:D_TIPCOUNT];
	maxtip_ = currenttip_;

	[self initTip:nil];

#if PUZZLEMODE_AVAILABLE
	[self addChild:puzzle_ z:MENUBUTTON_Z];
#endif // #if PUZZLEMODE_AVAILABLE
	[self addChild:single_ z:MENUBUTTON_Z];
	[self addChild:singlevsai_ z:MENUBUTTON_Z];
	[self addChild:twoplayer_ z:MENUBUTTON_Z];
	[self addChild:twoplayerreversed_ z:MENUBUTTON_Z];
	[self addChild:help_ z:MENUBUTTON_Z];
	[self addChild:credits_ z:MENUBUTTON_Z];
	[self addChild:tiparrows_[0] z:TIPARROWS_Z];
	[self addChild:tiparrows_[1] z:TIPARROWS_Z];
	single_.anchorPoint = ccp(0,0);
	singlevsai_.anchorPoint = ccp(0,0);
	twoplayer_.anchorPoint = ccp(0,0);
	twoplayerreversed_.anchorPoint = ccp(0,0);
	help_.anchorPoint = ccp(0,0);
	credits_.anchorPoint = ccp(0,0);
	single_.position = ccp(696, 768-136);
	singlevsai_.position = ccp(696, 768-219);
	twoplayer_.position = ccp(696, 768-364);
	twoplayerreversed_.position = ccp(696, 768-446);
	help_.position = ccp(696, 768-738);
	credits_.position = ccp(850, 768-738);
	tiparrows_[0].position = ccp(682+45, 768-568);
	tiparrows_[1].position = ccp(1009-55, 768-568);

	// Mascot animation
	for(int i = 0; i < 5; i++) {
		mascotbg_[i] = [[CCTextureCache sharedTextureCache] addImage:[NSString stringWithFormat:@"Assets/Graphics/menu/animation/frame%d.png", i+1]];
		mascotfg_[i] = [[CCTextureCache sharedTextureCache] addImage:[NSString stringWithFormat:@"Assets/Graphics/menu/animation/frame%d-thumbs.png", i+1]];
	}
	for(int i = 0; i < 3; i++) {
		manjump_[i] = [[CCTextureCache sharedTextureCache] addImage:[NSString stringWithFormat:@"Assets/Graphics/menu/animation/frame5-man%d.png", i+1]];
		bearjump_[i] = [[CCTextureCache sharedTextureCache] addImage:[NSString stringWithFormat:@"Assets/Graphics/menu/animation/frame5-bear%d.png", i+1]];
	}
	titlebeam_ = [CCSprite spriteWithFile:@"Assets/Graphics/menu/animation/block.png"];
	titlebeam_.anchorPoint = ccp(0,0);
	[self addChild:titlebeam_ z:TITLEBEAM_Z];

	arms_ = [CCSprite spriteWithFile:@"Assets/Graphics/menu/animation/arms.png"];
	arms_.anchorPoint = ccp(0,1);
	arms_.position = ccp(0, HELDTITLEPOS.y);
	arms_.visible = false;
	[self addChild:arms_ z:ROBOTARMS_Z];

	mascots_ = [CCSprite spriteWithTexture:mascotbg_[0]];
	mascots_.anchorPoint = ccp(0,0);
	[self addChild:mascots_ z:MENUMASCOT_Z];
	fingers_ = [CCSprite spriteWithTexture:mascotfg_[0]];
	fingers_.anchorPoint = ccp(0,0);
	[self addChild:fingers_ z:MENUFINGERS_Z];
	finalchars_[0] = [CCSprite spriteWithTexture:mascotfg_[0]];
	finalchars_[0].anchorPoint = ccp(0,0);
	finalchars_[0].visible = false;
	[self addChild:finalchars_[0] z:FINALCHARS_Z];
	finalchars_[1] = [CCSprite spriteWithTexture:mascotfg_[0]];
	finalchars_[1].anchorPoint = ccp(0,0);
	finalchars_[1].visible = false;
	[self addChild:finalchars_[1] z:FINALCHARS_Z];

	mantoucharea_ = [CCSprite spriteWithTexture:emptytex];
	mantoucharea_.position = ccp(90, 463 - 370);
	mantoucharea_.visible = false;
	[self addChild:mantoucharea_];
	beartoucharea_ = [CCSprite spriteWithTexture:emptytex];
	beartoucharea_.position = ccp(414, 463 - 370);
	beartoucharea_.visible = false;
	[self addChild:beartoucharea_];

	[self mainMenuAnim];

	return self;
}

-(void) initTip:(id)sender {
	[self removeChild:tip_[0] cleanup:true];
	[self removeChild:tip_[1] cleanup:true];
	[self removeChild:tip_[2] cleanup:true];

	int newtipno = (maxtip_ + 1) % ARRAYSIZE(tips);
	[[NSUserDefaults standardUserDefaults] setInteger:newtipno forKey:D_TIPCOUNT];

	int prevtipno = (currenttip_ - 1) % ARRAYSIZE(tips);
	int tipno = currenttip_;
	int nexttipno = (currenttip_ + 1) % ARRAYSIZE(tips);

	tiparrows_[0].visible = (tipno != 0);
	tiparrows_[1].visible = (tipno != ARRAYSIZE(tips) - 1);

	for(int i = 0; i < 3; i++) {
		int no;
		CGPoint pos;
		switch(i) {
			case 0:
				no = prevtipno;
				pos = PREVTIPPOS;
				break;
			case 1:
				no = tipno;
				pos = TIPPOS;
				break;
			case 2:
			default:
				no = nexttipno;
				pos = NEXTTIPPOS;
				break;
		}
		int fontsize = 20;// strlen(tips[no]) < 90 ? 24 : 20;
		NSString* str = [NSString stringWithUTF8String:tips[no]];
		CGSize box = CGSizeMake(293, 190);
		ZFont* font = [[FontManager sharedManager] zFontWithName:@"BPreplay" pointSize:fontsize];
		CGSize tipsize = [str sizeWithZFont:font constrainedToSize:box lineBreakMode:UILineBreakModeWordWrap];
		tip_[i] = [CCLabel labelWithString:str
							   dimensions:box
							   alignment:UITextAlignmentCenter
							   fontName:@"BPreplay"
							   fontSize:fontsize];
		tip_[i].anchorPoint = ccp(0.5, 1.0);
		tip_[i].position = ccp(pos.x, pos.y + tipsize.height/2);
		
	}
	tipswitching_ = false;
	[self addChild:tip_[0] z:TIP_Z];
	[self addChild:tip_[1] z:TIP_Z];
	[self addChild:tip_[2] z:TIP_Z];
}

-(void) switchTip:(bool)prev {
	int sideindex;
	CGPoint targetpos;
	if(prev) {
		sideindex = 0;
		targetpos = ccp(NEXTTIPPOS.x, [tip_[1] position].y);
		currenttip_--;
	} else {
		sideindex = 2;
		targetpos = ccp(PREVTIPPOS.x, [tip_[1] position].y);
		currenttip_++;
	}
	currenttip_ = currenttip_ % ARRAYSIZE(tips);
	tipswitching_ = true;

	[tip_[1] runAction:[CCSequence actions:
				[CCMoveTo actionWithDuration:0.5 position:targetpos],
				[CCCallFuncN actionWithTarget:self selector:@selector(initTip:)],
				nil]];
	[tip_[sideindex] runAction:[CCSequence actions:
				[CCMoveTo actionWithDuration:0.5 position:ccp(TIPPOS.x, [tip_[sideindex] position].y)],
				nil]];

	[scene playSound:@"Assets/Sounds/click.caf" volume:1.0];
}

-(void) finalPlacement {
	titlebeam_.position = FINALTITLEPOS;
}

- (void) landSound:(id)sender {
	[scene playSound:@"Assets/Sounds/trash.caf" volume:1.0];
}

-(void) mainMenuAnim {
	titlebeam_.position = STARTTITLEPOS;
	float droptime = 2.0;
	float crouchtime = 0.25;
	float lifttime = 0.05;
	float waittime = 4.0;
	float robolifttime = 5.0;
	[titlebeam_ runAction:[CCSequence actions:
		[CCEaseIn actionWithAction:[CCMoveTo actionWithDuration:droptime position:CROUCHEDTITLEPOS] rate:5.0f],
        [CCCallFuncN actionWithTarget:self selector:@selector(landSound:)],
	    [CCDelayTime actionWithDuration:crouchtime],
		[CCEaseIn actionWithAction:[CCMoveTo actionWithDuration:lifttime position:HELDTITLEPOS] rate:5.0f],
	    [CCDelayTime actionWithDuration:waittime],
		[CCMoveTo actionWithDuration:robolifttime position:FINALTITLEPOS],
		nil]];

	[mascots_ runAction:[CCSequence actions:
	    [CCDelayTime actionWithDuration:droptime],
		[SetTextureAction actionWithTexture:mascotbg_[2]],
	    [CCDelayTime actionWithDuration:crouchtime],
	    [CCDelayTime actionWithDuration:lifttime],
		[SetTextureAction actionWithTexture:mascotbg_[3]],
	    [CCDelayTime actionWithDuration:waittime],
		[SetTextureAction actionWithTexture:mascotbg_[4]],
		nil]];

	[fingers_ runAction:[CCSequence actions:
	    [CCDelayTime actionWithDuration:droptime],
		[SetTextureAction actionWithTexture:mascotfg_[2]],
	    [CCDelayTime actionWithDuration:crouchtime],
	    [CCDelayTime actionWithDuration:lifttime],
		[SetTextureAction actionWithTexture:mascotfg_[3]],
	    [CCDelayTime actionWithDuration:waittime],
		[CCHide action],
		nil]];

	[finalchars_[0] runAction:[CCSequence actions:
	    [CCDelayTime actionWithDuration:droptime],
	    [CCDelayTime actionWithDuration:crouchtime],
	    [CCDelayTime actionWithDuration:lifttime],
	    [CCDelayTime actionWithDuration:waittime],
		[CCShow action],
		[SetTextureAction actionWithTexture:manjump_[0]],
		nil]];

	[finalchars_[1] runAction:[CCSequence actions:
	    [CCDelayTime actionWithDuration:droptime],
	    [CCDelayTime actionWithDuration:crouchtime],
	    [CCDelayTime actionWithDuration:lifttime],
	    [CCDelayTime actionWithDuration:waittime],
		[CCShow action],
		[SetTextureAction actionWithTexture:bearjump_[0]],
		nil]];

	[arms_ runAction:[CCSequence actions:
	    [CCDelayTime actionWithDuration:droptime],
	    [CCDelayTime actionWithDuration:crouchtime],
	    [CCDelayTime actionWithDuration:lifttime],
		[CCShow action],
	    [CCDelayTime actionWithDuration:waittime],
		[CCMoveTo actionWithDuration:robolifttime position:ccp(0,FINALTITLEPOS.y)],
		nil]];
}


- (void)ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
	bool playsound = false;
	for(UITouch* touch in [touches allObjects]) {
		// Game mode select
		CGSize isize = [singlevsai_ contentSize];
		CGRect irect = CGRectMake(0,0,isize.width,isize.height);
#if PUZZLEMODE_AVAILABLE
		if(CGRectContainsPoint(irect, [puzzle_ convertTouchToNodeSpace:touch])) {
			scene->singleplayergrid.practice = false;
			scene->singleplayergrid.puzzle = true;
			[scene changeLayout:SINGLEPLAYER];
			[scene changeState:PLAYING];
			playsound = true;
		} else
#endif // #if PUZZLEMODE_AVAILABLE
		if(CGRectContainsPoint(irect, [single_ convertTouchToNodeSpace:touch])) {
			scene->singleplayergrid.practice = false;
			scene->singleplayergrid.puzzle = false;
			[scene changeLayout:SINGLEPLAYER];
			[scene changeState:PLAYING];
			playsound = true;
		} else if(CGRectContainsPoint(irect, [singlevsai_ convertTouchToNodeSpace:touch])) {
			[scene changeLayout:SINGLEVSAI];
			[scene changeState:PLAYING];
			playsound = true;
		} else if(CGRectContainsPoint(irect, [twoplayer_ convertTouchToNodeSpace:touch])) {
			[scene changeLayout:TWOPLAYERS];
			[scene changeState:PLAYING];
			playsound = true;
		} else if(CGRectContainsPoint(irect, [twoplayerreversed_ convertTouchToNodeSpace:touch])) {
			[scene changeLayout:TWOPLAYERSREVERSED];
			[scene changeState:PLAYING];
			playsound = true;
		}

		// Tip switching
		isize = [tiparrows_[0] contentSize];
		irect = CGRectMake(0,0,isize.width,isize.height);
		if(!tipswitching_ && tiparrows_[0].visible && CGRectContainsPoint(irect, [tiparrows_[0] convertTouchToNodeSpace:touch])) {
			[self switchTip:true];
		} else if(!tipswitching_ && tiparrows_[1].visible && CGRectContainsPoint(irect, [tiparrows_[1] convertTouchToNodeSpace:touch])) {
			[self switchTip:false];
		}

		// Instructions and credits
		isize = [help_ contentSize];
		irect = CGRectMake(0,0,isize.width,isize.height);
		if(CGRectContainsPoint(irect, [help_ convertTouchToNodeSpace:touch])) {
			[scene changeState:INSTRUCTIONS];
		}
		if(CGRectContainsPoint(irect, [credits_ convertTouchToNodeSpace:touch])) {
			[scene changeState:CREDITS];
		}

		// jump animations
		float inbetweentime = 0.05;
		float hightime = 0.2;
		isize.width = MANTOUCHSIZE.x;  isize.height = MANTOUCHSIZE.y;
		irect = CGRectMake(0,0,isize.width,isize.height);
		if(finalchars_[0].texture == manjump_[0] && CGRectContainsPoint(irect, [mantoucharea_ convertTouchToNodeSpace:touch])) {
			[scene playSound:@"Assets/Sounds/hat.caf" volume:0.5];
			[finalchars_[0] runAction:[CCSequence actions:
				[SetTextureAction actionWithTexture:manjump_[1]],
				[CCDelayTime actionWithDuration:inbetweentime],
				[SetTextureAction actionWithTexture:manjump_[2]],
				[CCDelayTime actionWithDuration:hightime],
				[SetTextureAction actionWithTexture:manjump_[1]],
				[CCDelayTime actionWithDuration:inbetweentime],
				[SetTextureAction actionWithTexture:manjump_[0]],
				nil]];
		}
		if(finalchars_[1].texture == bearjump_[0] && CGRectContainsPoint(irect, [beartoucharea_ convertTouchToNodeSpace:touch])) {
			[scene playSound:@"Assets/Sounds/hat.caf" volume:0.5];
			[finalchars_[1] runAction:[CCSequence actions:
				[SetTextureAction actionWithTexture:bearjump_[1]],
				[CCDelayTime actionWithDuration:inbetweentime],
				[SetTextureAction actionWithTexture:bearjump_[2]],
				[CCDelayTime actionWithDuration:hightime],
				[SetTextureAction actionWithTexture:bearjump_[1]],
				[CCDelayTime actionWithDuration:inbetweentime],
				[SetTextureAction actionWithTexture:bearjump_[0]],
				nil]];
		}	
	}

	if(playsound) {
		[scene playSound:@"Assets/Sounds/chain00.caf" volume:1.0];
	}

}

@end

@implementation Instructions

-(id) init:(GameState)oldstate showCredits:(bool)showcredits {
	if(!(self = [super init])) return nil;

	self.isTouchEnabled = true;

	tutorialgrid_ = scene->base2playergrid;
	tutorialgrid_.mascot = KEITH;
	tutorialgrid_.difficulty = MEDIUM;
	if(showcredits) {
		[scene playSound:@"Assets/Sounds/fanfare-long.caf" volume:1.0];
	}

	oldstate_ = oldstate;
	showcredits_ = showcredits;
	pageno_ = 1;
	[self updatePage];

	return self;
}

- (void) updatePage {
	[self removeChild:page_ cleanup:true];
	if(showcredits_) {
		page_ = [CCSprite spriteWithFile:@"Assets/Graphics/credits.png"];
	} else {
		page_ = [CCSprite spriteWithFile:[NSString
								  stringWithFormat:@"Assets/Graphics/instructions%d.png", pageno_]];
	}

	page_.position = ccp(0, 0);
	page_.anchorPoint = ccp(0, 0);
	[self addChild:page_];

	if(showcredits_) return;

	[scene playSound:@"Assets/Sounds/click.caf" volume:1.0];

	[self removeAnimations:nil];
	int fontsize = 20;
	if(pageno_ == 1) {
		nextbutton_ = [CCSprite spriteWithFile:@"Assets/Graphics/instructions-btn-1.png"];
		nextbutton_.position = ccp(568, 768-82);
		nextbutton_.anchorPoint = ccp(0, 0);
		[self addChild:nextbutton_];

		exitbutton_ = [CCSprite spriteWithFile:@"Assets/Graphics/instructions-btn-exit.png"];
		exitbutton_.position = ccp(990, 768-82);
		exitbutton_.anchorPoint = ccp(1, 0);
		[self addChild:exitbutton_];

		label1_ = [CCLabel labelWithString:@"Placing beams" fontName:@"BPreplay" fontSize:fontsize];
		label2_ = [CCLabel labelWithString:@"Matching" fontName:@"BPreplay" fontSize:fontsize];
		label1_.anchorPoint = label2_.anchorPoint = ccp(0, 1);
		label1_.position = ccp(567+10, 768-208-8);
		label2_.position = ccp(567+10, 768-489-8);
		[self addChild:label1_];
		[self addChild:label2_];
		[self runAction:[CCRepeatForever actionWithAction:[CCSequence actions:
				  [CCCallFuncN actionWithTarget:self selector:@selector(placeAnimation:)],
				  [CCDelayTime actionWithDuration:10.0],
				nil]]];

		[self runAction:[CCRepeatForever actionWithAction:[CCSequence actions:
				  [CCCallFuncN actionWithTarget:self selector:@selector(fallMatchAnimation:)],
				  [CCDelayTime actionWithDuration:5.0],
				nil]]];
	}
	if(pageno_ == 2) {
		nextbutton_ = [CCSprite spriteWithFile:@"Assets/Graphics/instructions-btn-2.png"];
		nextbutton_.position = ccp(568, 768-82);
		nextbutton_.anchorPoint = ccp(0, 0);
		[self addChild:nextbutton_];

		exitbutton_ = [CCSprite spriteWithFile:@"Assets/Graphics/instructions-btn-exit.png"];
		exitbutton_.position = ccp(990, 768-82);
		exitbutton_.anchorPoint = ccp(1, 0);
		[self addChild:exitbutton_];
	}

	if(pageno_ == 3) {
		exitbutton_ = [CCSprite spriteWithFile:@"Assets/Graphics/instructions-btn-exit.png"];
		exitbutton_.position = ccp(990, 768-82);
		exitbutton_.anchorPoint = ccp(1, 0);
		[self addChild:exitbutton_];

		label1_ = [CCLabel labelWithString:@"Example combo" fontName:@"BPreplay" fontSize:fontsize];
		label2_ = [CCLabel labelWithString:@"Example chain" fontName:@"BPreplay" fontSize:fontsize];
		label1_.anchorPoint = label2_.anchorPoint = ccp(0, 1);
		label1_.position = ccp(567+10, 768-142-8);
		label2_.position = ccp(567+10, 768-436-8);
		[self addChild:label1_];
		[self addChild:label2_];
		[self runAction:[CCRepeatForever actionWithAction:[CCSequence actions:
				  [CCCallFuncN actionWithTarget:self selector:@selector(comboAnimation:)],
				  [CCDelayTime actionWithDuration:4.0],
				nil]]];
		[self runAction:[CCRepeatForever actionWithAction:[CCSequence actions:
				  [CCCallFuncN actionWithTarget:self selector:@selector(chainAnimation:)],
				  [CCDelayTime actionWithDuration:9.0],
				nil]]];
	}
}

- (void) ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
	bool nextpage = false;
	bool exit = false;
	if(showcredits_) {
		exit = true;
	} else {
		for(UITouch* touch in [touches allObjects]) {
			CGSize butsize = [exitbutton_ contentSize];
			CGRect butrect = CGRectMake(0,0,butsize.width,butsize.height);
			if(CGRectContainsPoint(butrect, [exitbutton_ convertTouchToNodeSpace:touch])) {
				exit = true;
			}
			if(pageno_ < 3) {
				butsize = [nextbutton_ contentSize];
				butrect = CGRectMake(0,0,butsize.width,butsize.height);
				if(CGRectContainsPoint(butrect, [nextbutton_ convertTouchToNodeSpace:touch])) {
					nextpage = true;
				}
			}
		}
	}

	if(exit) {
		[scene changeState:oldstate_];
		[scene playSound:@"Assets/Sounds/click.caf" volume:1.0];
	} else {
		if(nextpage) {
			pageno_++;
			[self updatePage];
		}
	}
}

- (void)removeAnimations:(id) sender {
	[self stopAllActions];
	[self removeChild:nextbutton_ cleanup:true];
	[self removeChild:exitbutton_ cleanup:true];
	[self removeChild:label1_ cleanup:true];
	[self removeChild:label2_ cleanup:true];
	[self removeChild:placebox_ cleanup:true];
	[self removeChild:fallmatchbox_ cleanup:true];
	[self removeChild:chainbox_ cleanup:true];
	label1_ = label2_ = nil;
	placebox_ = fallmatchbox_ = chainbox_ = nil;
}

- (void)placeAnimation:(id)sender {
	[self removeChild:placebox_ cleanup:true];
	placebox_ = [CCNode node];
	placebox_.position = ccp(567, 768-460);
	[self addChild:placebox_];

	GridConfig* g = &tutorialgrid_;
	int xsize = g->tilesize_x;
	int ysize = g->tilesize_y;

	CGPoint leftpos = ccp(xsize*2.5, ysize*0.5);
	CGPoint rightpos = ccp(xsize*5.5, ysize*0.5);
	CGPoint uppos = ccp(xsize*7.5, ysize*1.5);
	CGPoint downpos = ccp(xsize*8.5, ysize*1.5);

	CGPoint jumpstartpos = ccp(-100, -100);

	tutorialblock_[0] = [Block blockInGrid:g pos:ccp(-1,-1)
							color:GREEN
							shape:LEFT];
	tutorialblock_[1] = [Block blockInGrid:g pos:ccp(-1,-1)
							color:GREEN
							shape:RIGHT];
	tutorialblock_[2] = [Block blockInGrid:g pos:ccp(-1,-1)
							color:GREEN
							shape:UP];
	tutorialblock_[3] = [Block blockInGrid:g pos:ccp(-1,-1)
							color:GREEN
							shape:DOWN];
	leftblock_ = [Block blockInGrid:g pos:ccp(-1,-1)
							color:GREEN
							shape:LEFT];
	rightblock_ = [Block blockInGrid:g pos:ccp(-1,-1)
							color:GREEN
							shape:RIGHT];
	upblock_ = [Block blockInGrid:g pos:ccp(-1,-1)
							color:GREEN
							shape:UP];
	downblock_ = [Block blockInGrid:g pos:ccp(-1,-1)
							color:GREEN
							shape:DOWN];
	tutorialblock_[0]->clipped_ = false;
	tutorialblock_[1]->clipped_ = false;
	tutorialblock_[2]->clipped_ = false;
	tutorialblock_[3]->clipped_ = false;
	leftblock_->clipped_ = false;
	rightblock_->clipped_ = false;
	upblock_->clipped_ = false;
	downblock_->clipped_ = false;
	tutorialblock_[0].position = leftpos;
	tutorialblock_[1].position = rightpos;
	tutorialblock_[2].position = uppos;
	tutorialblock_[3].position = downpos;
	leftblock_.position = jumpstartpos;
	rightblock_.position = jumpstartpos;
	upblock_.position = jumpstartpos;
	downblock_.position = jumpstartpos;
	tutorialblock_[0].visible = false;
	tutorialblock_[1].visible = false;
	tutorialblock_[2].visible = false;
	tutorialblock_[3].visible = false;
	leftblock_.visible = false;
	rightblock_.visible = false;
	upblock_.visible = false;
	downblock_.visible = false;

	[placebox_ addChild:tutorialblock_[0]];
	[placebox_ addChild:tutorialblock_[1]];
	[placebox_ addChild:tutorialblock_[2] z:2];
	[placebox_ addChild:tutorialblock_[3]];
	[placebox_ addChild:leftblock_];
	[placebox_ addChild:rightblock_];
	[placebox_ addChild:upblock_];
	[placebox_ addChild:downblock_];

	[tutorialblock_[0] makeTutorialBlock:nil];
	[tutorialblock_[0] runAction:[CCSequence actions:
          [CCShow action],
          [CCDelayTime actionWithDuration:TUTORIALDELAY],
          [CCHide action],
		  nil]];
	[tutorialblock_[1] runAction:[CCSequence actions:
          [CCDelayTime actionWithDuration:TUTORIALDELAY],
		  [CCCallFuncN actionWithTarget:tutorialblock_[1] selector:@selector(makeTutorialBlock:)],
          [CCShow action],
          [CCDelayTime actionWithDuration:TUTORIALDELAY],
          [CCHide action],
		  nil]];
	[tutorialblock_[2] runAction:[CCSequence actions:
          [CCDelayTime actionWithDuration:TUTORIALDELAY*2],
		  [CCCallFuncN actionWithTarget:tutorialblock_[2] selector:@selector(makeTutorialBlock:)],
          [CCShow action],
          [CCDelayTime actionWithDuration:TUTORIALDELAY],
          [CCHide action],
		  nil]];
	[tutorialblock_[3] runAction:[CCSequence actions:
          [CCDelayTime actionWithDuration:TUTORIALDELAY*3],
		  [CCCallFuncN actionWithTarget:tutorialblock_[3] selector:@selector(makeTutorialBlock:)],
          [CCShow action],
          [CCDelayTime actionWithDuration:TUTORIALDELAY],
          [CCHide action],
		  nil]];

	[leftblock_ runAction:[CCSequence actions:
          [CCDelayTime actionWithDuration:TUTORIALDELAY],
          [CCShow action],
		  [CCJumpTo actionWithDuration:SPAWNDELAY position:leftpos height:100 jumps:1],
		  nil]];
	[rightblock_ runAction:[CCSequence actions:
          [CCDelayTime actionWithDuration:TUTORIALDELAY*2],
          [CCShow action],
		  [CCJumpTo actionWithDuration:SPAWNDELAY position:rightpos height:100 jumps:1],
		  nil]];
	[upblock_ runAction:[CCSequence actions:
          [CCDelayTime actionWithDuration:TUTORIALDELAY*3],
          [CCShow action],
		  [CCJumpTo actionWithDuration:SPAWNDELAY position:uppos height:100 jumps:1],
		  nil]];
	[downblock_ runAction:[CCSequence actions:
          [CCDelayTime actionWithDuration:TUTORIALDELAY*4],
          [CCShow action],
		  [CCJumpTo actionWithDuration:SPAWNDELAY position:downpos height:100 jumps:1],
		  nil]];
}

- (void)fallMatchAnimation:(id)sender {
	[self removeChild:fallmatchbox_ cleanup:true];
	fallmatchbox_ = [CCNode node];
	fallmatchbox_.position = ccp(567, 768-740);
	[self addChild:fallmatchbox_];

	GridConfig* g = &tutorialgrid_;
	int xsize = g->tilesize_x;
	int ysize = g->tilesize_y;
	int xoffset = xsize*4.5;
	int yoffset = ysize*1.5;

	CGPoint fallstartpos = ccp(xoffset+xsize, yoffset+ysize*3);

	Block* b1 = [Block blockInGrid:g pos:ccp(-1,-1)
							color:BLUE
							shape:UP];
	Block* b2 = [Block blockInGrid:g pos:ccp(-1,-1)
							color:RED
							shape:DOWN];
	matchblock_ = [Block blockInGrid:g pos:ccp(-1,-1)
							color:RED
							shape:DOWN];
	fallblock_ = [Block blockInGrid:g pos:ccp(-1,-1)
							color:RED
							shape:DOWN];
	falltutorial_ = [Block blockInGrid:g pos:ccp(-1,-1)
							color:RED
							shape:DOWN];
	b1->clipped_ = false;
	b2->clipped_ = false;
	matchblock_->clipped_ = false;
	fallblock_->clipped_ = false;
	falltutorial_->clipped_ = false;
	fallblock_.visible = false;

	b1.position = ccp(xoffset+xsize * 2, yoffset+0);
	b2.position = ccp(xoffset+xsize * 2, yoffset+ysize*3);
	matchblock_.position = ccp(xoffset+0, yoffset+0);
	fallblock_.position = ccp(-100, -100);
	falltutorial_.position = fallstartpos;

	[fallmatchbox_ addChild:b1];
	[fallmatchbox_ addChild:b2];
	[fallmatchbox_ addChild:matchblock_];
	[fallmatchbox_ addChild:fallblock_];
	[fallmatchbox_ addChild:falltutorial_];

	[falltutorial_ makeTutorialBlock:nil];
	[falltutorial_ runAction:[CCSequence actions:
          [CCShow action],
          [CCDelayTime actionWithDuration:TUTORIALDELAY],
          [CCHide action],
		  nil]];

	[fallblock_ runAction:[CCSequence actions:
          [CCDelayTime actionWithDuration:TUTORIALDELAY],
		  [CCShow action],
		  [CCJumpTo actionWithDuration:SPAWNDELAY position:ccp(xoffset+xsize, yoffset+ysize*2) height:100 jumps:1],
          [CCMoveTo actionWithDuration:3/FALLSPEED position:ccp(xoffset+xsize, yoffset+0)],
		  [MeltBlockAction action],
		  nil]];

	[matchblock_ runAction:[CCSequence actions:
          [CCDelayTime actionWithDuration:TUTORIALDELAY],
          [CCDelayTime actionWithDuration:SPAWNDELAY],
          [CCDelayTime actionWithDuration:3/FALLSPEED],
		  [MeltBlockAction action],
		  nil]];
}

- (void)comboAnimation:(id)sender {
	[self removeChild:combobox_ cleanup:true];
	combobox_ = [CCNode node];
	combobox_.position = ccp(567+168, 768-393);
	[self addChild:combobox_];

	GridConfig* g = &tutorialgrid_;
	int xsize = g->tilesize_x;
	int ysize = g->tilesize_y;

	combotutorial_ = [Block blockInGrid:g pos:ccp(-1,-1)
							color:RED
							shape:UP];
	place_ = [Block blockInGrid:g pos:ccp(-1,-1)
							color:RED
							shape:UP];
	double_ = [Block blockInGrid:g pos:ccp(-1,-1)
							color:RED
							shape:UPDOWN];
	single_ = [Block blockInGrid:g pos:ccp(-1,-1)
							color:RED
							shape:UP];
	combotutorial_->clipped_ = false;
	place_->clipped_ = false;
	double_->clipped_ = false;
	single_->clipped_ = false;

	combotutorial_.position = ccp(xsize * 1.5, ysize*1.5);
	place_.position = ccp(-100, -100);
	place_.visible = false;
	double_.position = ccp(xsize * 2.5, ysize*1.5);
	single_.position = ccp(xsize * 0.5, ysize*1.5);

	CGPoint placepos = combotutorial_.position;

	[combobox_ addChild:combotutorial_ z:2];
	[combobox_ addChild:place_];
	[combobox_ addChild:double_];
	[combobox_ addChild:single_];

	float delay = TUTORIALDELAY;
	[combotutorial_ makeTutorialBlock:nil];
	[combotutorial_ runAction:[CCSequence actions:
          [CCShow action],
          [CCDelayTime actionWithDuration:delay],
          [CCHide action],
		  nil]];

	[place_ runAction:[CCSequence actions:
          [CCDelayTime actionWithDuration:delay],
		  [CCShow action],
		  [CCJumpTo actionWithDuration:SPAWNDELAY position:placepos height:100 jumps:1],
		  [MeltBlockAction action],
		  nil]];
	delay += SPAWNDELAY;
	[single_ runAction:[CCSequence actions:
          [CCDelayTime actionWithDuration:delay],
		  [MeltBlockAction action],
		  nil]];
	[double_ runAction:[CCSequence actions:
          [CCDelayTime actionWithDuration:delay],
		  [MeltBlockAction action],
		  [CCCallFuncN actionWithTarget:self selector:@selector(comboCircle:)],
		  nil]];
}

- (void)comboCircle:(id)sender {
	CGPoint boxpos = [PuzzleStack iconPosIsCombo:true block1:double_ block2:place_ pxxsize:400 pxysize:400]; 
	boxpos.y += 50;
	[combobox_ addChild:[PuzzleStack comboCircleWithCount:4 pos:boxpos]];
}

- (void)chainAnimation:(id)sender {
	[self removeChild:chainbox_ cleanup:true];
	chainbox_ = [CCNode node];
	chainbox_.position = ccp(567+168, 768-727);
	[self addChild:chainbox_];

	GridConfig* g = &tutorialgrid_;
	int xsize = g->tilesize_x;
	int ysize = g->tilesize_y;

	chaintutorial_ = [Block blockInGrid:g pos:ccp(-1,-1)
							color:BLUE
							shape:UP];
	vertical_[0] = [Block blockInGrid:g pos:ccp(-1,-1)
							color:BLUE
							shape:UP];
	vertical_[1] = [Block blockInGrid:g pos:ccp(-1,-1)
							color:BLUE
							shape:UP];
	chain1_[0] = [Block blockInGrid:g pos:ccp(-1,-1)
							color:GREEN
							shape:LEFT];
	chain1_[1] = [Block blockInGrid:g pos:ccp(-1,-1)
							color:GREEN
							shape:LEFT];
	chain2_[0] = [Block blockInGrid:g pos:ccp(-1,-1)
							color:RED
							shape:LEFT];
	chain2_[1] = [Block blockInGrid:g pos:ccp(-1,-1)
							color:RED
							shape:LEFT];
	chaintutorial_->clipped_ = false;
	vertical_[0]->clipped_ = false;
	vertical_[1]->clipped_ = false;
	chain1_[0]->clipped_ = false;
	chain1_[1]->clipped_ = false;
	chain2_[0]->clipped_ = false;
	chain2_[1]->clipped_ = false;

	chaintutorial_.position = ccp(xsize * 1.5, ysize*3.5);
	vertical_[0].position = ccp(-100, -100);
	vertical_[0].visible = false;
	vertical_[1].position = ccp(xsize * 2.5, ysize*3.5);
	chain1_[0].position = ccp(xsize * 1.5, ysize * 1.5);
	chain1_[1].position = ccp(xsize * 1.5, ysize * 5.5);
	chain2_[0].position = ccp(xsize * 1.5, ysize * 0.5);
	chain2_[1].position = ccp(xsize * 1.5, ysize * 6.5);

	CGPoint placepos = [chaintutorial_ position]; 
	CGPoint fallpos1 = ccp([chain1_[0] position].x, [chain1_[0] position].y + ysize);
	CGPoint fallpos2 = ccp([chain1_[0] position].x, [chain1_[0] position].y);

	[chainbox_ addChild:chaintutorial_];
	[chainbox_ addChild:vertical_[0]];
	[chainbox_ addChild:vertical_[1]];
	[chainbox_ addChild:chain1_[0]];
	[chainbox_ addChild:chain1_[1]];
	[chainbox_ addChild:chain2_[0]];
	[chainbox_ addChild:chain2_[1]];

	float delay = TUTORIALDELAY;
	[chaintutorial_ makeTutorialBlock:nil];
	[chaintutorial_ runAction:[CCSequence actions:
          [CCShow action],
          [CCDelayTime actionWithDuration:delay],
          [CCHide action],
		  nil]];

	[vertical_[0] runAction:[CCSequence actions:
          [CCDelayTime actionWithDuration:delay],
		  [CCShow action],
		  [CCJumpTo actionWithDuration:SPAWNDELAY position:placepos height:100 jumps:1],
		  [MeltBlockAction action],
		  nil]];
	delay += SPAWNDELAY;
	[vertical_[1] runAction:[CCSequence actions:
          [CCDelayTime actionWithDuration:delay],
		  [MeltBlockAction action],
		  nil]];

	delay += MEDIUMMELTTIME;
	[chain1_[1] runAction:[CCSequence actions:
          [CCDelayTime actionWithDuration:delay],
          [CCMoveTo actionWithDuration:3/FALLSPEED position:fallpos1],
		  [MeltBlockAction action],
		  [CCCallFuncN actionWithTarget:self selector:@selector(chainStar1:)],
		  nil]];

	[chain1_[0] runAction:[CCSequence actions:
          [CCDelayTime actionWithDuration:delay+ (3/FALLSPEED)],
		  [MeltBlockAction action],
		  nil]];

	[chain2_[1] runAction:[CCSequence actions:
          [CCDelayTime actionWithDuration:delay],
          [CCMoveTo actionWithDuration:3/FALLSPEED position:placepos],
          [CCDelayTime actionWithDuration:MEDIUMMELTTIME],
          [CCMoveTo actionWithDuration:2/FALLSPEED position:fallpos2],
		  [MeltBlockAction action],
		  [CCCallFuncN actionWithTarget:self selector:@selector(chainStar2:)],
		  nil]];

	delay += 3/FALLSPEED;
	delay += MEDIUMMELTTIME;
	delay += 2/FALLSPEED;
	[chain2_[0] runAction:[CCSequence actions:
          [CCDelayTime actionWithDuration:delay],
		  [MeltBlockAction action],
		  nil]];
	delay += MEDIUMMELTTIME;
}

- (void)chainStar1:(id)sender {
	CGPoint boxpos = [PuzzleStack iconPosIsCombo:false block1:chain1_[0] block2:chain1_[1] pxxsize:400 pxysize:400]; 
	[chainbox_ addChild:[PuzzleStack chainStarWithCount:2 start:boxpos end:ccp(1000, boxpos.y)]];
}


- (void)chainStar2:(id)sender {
	CGPoint boxpos = [PuzzleStack iconPosIsCombo:false block1:chain2_[0] block2:chain2_[1] pxxsize:400 pxysize:400]; 
	[chainbox_ addChild:[PuzzleStack chainStarWithCount:3 start:boxpos end:ccp(1000, boxpos.y)]];
}

@end

@implementation PuzzleCommon

-(id) initWithGridConfig:(GridConfig*) grid {
	if( (self=[super init] )) {
		g_ = grid;

		blocktree_ = [[NSMutableArray alloc] init];

		blockcolors_ = [[NSMutableArray alloc] init];
		blockdoubleness_ = [[NSMutableArray alloc] init];

		if(g_->puzzle) {
			[self loadPredefinedLevel:predefinedgrid startheight:0];
		} else {
			[self generateRandomTreeWithDepth:GENTREE_HEIGHT startheight:0];
		}
	}

	return self;
}

- (void) dealloc {
	[blocktree_ release];
	[blockcolors_ release];
	[blockdoubleness_ release];

	[super dealloc];
}

- (void) getBlocksAtRaiseLevel:(int)raiselevel blockarray:(Block**)blockarray {
	for(int i = 0; i < GRID_XSIZE; i++) {
		blockarray[i] = nil;
	}

	Block* b2;
	for(b2 in blocktree_) {
		CGRect rect = [b2 gridRect];
		if(CGRectGetMaxY(rect) == raiselevel) {
			int x = CGRectGetMinX(rect);
			blockarray[x] = b2;
		}
	}
}

BlockColor randomColor(BlockColor prevcolor) {
	if(prevcolor == EMPTY) {
		return (BlockColor) (myrandom(3) + RED);
	} else {
	    // 1/5 chance of same color as previous one
		if (myrandom(5) == 0) { return prevcolor; }
		BlockColor other1, other2;
		switch (prevcolor) {
			case RED: other1 = GREEN; other2 = BLUE; break;
			case GREEN: other1 = RED; other2 = BLUE; break;
			case BLUE: other1 = RED; other2 = GREEN; break;
			default: assert(false); break;
		}
		// 50% chance between two other colors
		if (myrandom(2) == 0) { return other1; } else { return other2; }
	}
}

BlockShape randomShape(BlockShape prevshape) {
	bool isHoriz;
	if(prevshape == 0) {
		isHoriz = myrandom(2);
	} else {
		// 1/8 chance of same orientation as previous one
		isHoriz = myrandom(8) == 0;
		if(prevshape == UP || prevshape == DOWN) { isHoriz = !isHoriz; }
	}

	if(isHoriz) {
		if(myrandom(2) == 0 ) { return LEFT; } else { return RIGHT; }
	} else {
		if(myrandom(2) == 0 ) { return UP; } else { return DOWN; }
	}
}

BOOL randomDoubleness() {
	return myrandom(6) == 0;
}


-(void) generateRandomTreeWithDepth:(int)depth startheight:(int)startheight {
	NSMutableArray* randomtree = [[[NSMutableArray alloc] init] autorelease];

	// Simulate a lot of blocks falling randomly from above with a floor,
	// and stop when one of the blocks reaches 0
	int floor[GRID_XSIZE];
	int i;
	int lowest = 0;
	for(i = 0; i < GRID_XSIZE; i++) { floor[i] = -depth; }

   	BlockShape s = LEFT;
	BlockColor c = EMPTY;
	int xpos;
	int ypos;
	Block* newblock;
	bool side = false;
	for(;;) {
		s = randomShape((BlockShape)0);
		c = randomColor(c);
		if((s & LEFT) || (s & RIGHT)) {
//			xpos = myrandom(6);
//			bool side = myrandom(2);
			// Bias toward edges slightly, to correct for increased likelihood of center tiles being covered
			int r = myrandom(100);
			if(r <= 40) { xpos = side ? 0 : 5; } else
			if(r <= 73) { xpos = side ? 1 : 4; } else {
				xpos = side ? 2 : 3; }
			ypos = MAX(MAX(floor[xpos], floor[xpos + 1]), floor[xpos + 2]);
		} else {
		   	xpos = myrandom(4) + side * 4;
			ypos = floor[xpos];
		}

		newblock = [Block blockInGrid:g_ pos:ccp(xpos,ypos) color:c shape:s];
		bool valid = true;
		for(i = lowest; i < (int) [randomtree count]; i++) {
			Block* b2 = [randomtree objectAtIndex:i];

			// Optimize by excluding blocks that falling blocks can't possibly touch
			if(i == lowest) {
				int minfloor = 100;
				for(int j = 0; j < GRID_XSIZE; j++) { minfloor = MIN(floor[j], minfloor); }
				if(b2.gridpos.y < minfloor - 5) { lowest++; continue; }
			}

			// avoid touching vertical blocks
			if((newblock.shape & UP || newblock.shape & DOWN) && (b2.shape & UP || b2.shape & DOWN)) {
				if(b2.gridpos.x == xpos - 1 || b2.gridpos.x == xpos + 1) {
					if(b2.gridpos.y >= ypos - 2 && b2.gridpos.y <= ypos + 2) {
						valid = false;
						break;
					}
				}

				if(b2.gridpos.y == ypos - 3 && b2.gridpos.x == xpos) {
					valid = false;
					break;
				}
			}

			// avoid horiz blocks stacked right on each other
			if((newblock.shape & LEFT || newblock.shape & RIGHT) && (b2.shape & LEFT || b2.shape & RIGHT)) {
				if(b2.gridpos.y == ypos - 1 && b2.gridpos.x == xpos) {
					valid = false;
					break;
				}
			}

			// avoid heart matches
			if(newblock.col == b2.col) {
				CGPoint heartpos1 = [newblock heartPos];
				CGPoint heartpos2 = [b2 heartPos];
				if(heartpos1.x == heartpos2.x && (ABS(heartpos1.y - heartpos2.y) == 1)) {
					valid = false;
					break;
				}
				if(heartpos1.y == heartpos2.y && (ABS(heartpos1.x - heartpos2.x) == 1)) {
					valid = false;
					break;
				}
			}
		}

		if(valid == true) {
			if(ypos >= 0) break;
			[randomtree addObject:newblock];
			if((s & LEFT) || (s & RIGHT)) {
				floor[xpos] = floor[xpos + 1] = floor[xpos + 2] = ypos + 1;
			} else {
				floor[xpos] += 3;
			}
			side = side ? 0 : 1;
		}
	}

	int maxfloor = -10000;
	for(int j = 0; j < GRID_XSIZE; j++) { maxfloor = MAX(floor[j], maxfloor); }
	for(i = 0; i < (int) [randomtree count]; i++) {
		Block* b2 = [randomtree objectAtIndex:i];
		CGPoint gridpos = b2.gridpos;
		gridpos.y += startheight - maxfloor - 1;
		b2.gridpos = gridpos;
		[blocktree_ addObject:b2];
	}
	treedepth_ += depth + maxfloor;
}

-(void) loadPredefinedLevel:(const char*) level startheight:(int)startheight {
	int totallen = strlen(level);
	assert(totallen % 9 == 0);
	char* p = new char[totallen];
	memcpy(p, level, totallen);

#define ISCOL(x) (x == 'R' || x == 'G' || x == 'B')
#define GETCOL(x) (x == 'R' ? RED : (x == 'G' ? GREEN : BLUE))

	int s = startheight - 1;
	int x, y;
	int offset;
	Block* newblock = nil;

	// Create horiz blocks
	for(y = 0; y < totallen / 9; y++) {
		for(x = 1; x < 7; x++) {
			offset = y*9+x;
			if(p[offset] == '-') {
				if((p[offset-1] == '-' || p[offset-1] == '=') && ISCOL(p[offset+1])) {
					newblock = [Block blockInGrid:g_
									pos:ccp(x-1,-y-1+s) color:GETCOL(p[offset+1])
									shape:RIGHT];
				}
				if(ISCOL(p[offset-1]) && (p[offset+1] == '-' || p[offset+1] == '=')) {
					newblock = [Block blockInGrid:g_
									pos:ccp(x-1,-y-1+s) color:GETCOL(p[offset-1])
									shape:LEFT];
				}
				if(ISCOL(p[offset-1]) && ISCOL(p[offset+1])) {
					newblock = [Block blockInGrid:g_
									pos:ccp(x-1,-y-1+s) color:GETCOL(p[offset-1])
									shape:LEFTRIGHT];
				}
				if(newblock) {
					p[offset-1] = p[offset] = p[offset+1] = ' ';
					[blocktree_ addObject:newblock];
					newblock = nil;
				}
			}
		}
	}

	// Create vertical blocks
	for(y = 1; y < totallen / 9 - 1; y++) {
		for(x = 0; x < 8; x++) {
			offset = y*9+x;
			if(p[offset] == '|') {
				if(p[offset-9] == '|' && ISCOL(p[offset+9])) {
					newblock = [Block blockInGrid:g_
									pos:ccp(x,-y-2+s) color:GETCOL(p[offset+9])
									shape:DOWN];
				}
				if(ISCOL(p[offset-9]) && p[offset+9] == '|') {
					newblock = [Block blockInGrid:g_
									pos:ccp(x,-y-2+s) color:GETCOL(p[offset-9])
									shape:UP];
				}
				if(ISCOL(p[offset-9]) && ISCOL(p[offset+9])) {
					newblock = [Block blockInGrid:g_
									pos:ccp(x,-y-2+s) color:GETCOL(p[offset-9])
									shape:UPDOWN];
				}
				if(newblock) {
					p[offset-9] = p[offset] = p[offset+9] = ' ';
					[blocktree_ addObject:newblock];
					newblock = nil;
				}
			}
		}
	}

	// Create rubbish
	for(y = 0; y < totallen / 9; y++) {
		for(x = 0; x < 5; x++) {
			offset = y*9+x;
			if(p[offset] == '0') {
				newblock = [Block blockInGrid:g_
								pos:ccp(x,-y-1+s) color:TRASH
								shape:TRASHx0];
				p[offset] = p[offset+1] = p[offset+2] = p[offset+3] = ' ';
				p[offset+4] = p[offset+5] = p[offset+6] = p[offset+7] = ' ';
			}
			if(p[offset] == '1') {
				newblock = [Block blockInGrid:g_
								pos:ccp(x,-y-1+s) color:TRASH
								shape:TRASHx1];
				p[offset] = p[offset+1] = p[offset+2] = p[offset+3] = ' ';
			}
			if(p[offset] == '2') {
				newblock = [Block blockInGrid:g_
								pos:ccp(x,-y-2+s) color:TRASH
								shape:TRASHx2];
				p[offset] = p[offset+1] = p[offset+2] = p[offset+3] = ' ';
				p[offset+9] = p[offset+1+9] = p[offset+2+9] = p[offset+3+9] = ' ';
			}
			if(newblock) {
				[blocktree_ addObject:newblock];
				newblock = nil;
			}
		}
	}

	// Assert that everything was loaded properly (only empty space left)
	for(offset = 0; offset < totallen; offset++) {
		if(offset % 9 == 8) assert(p[offset] == '.');
		else assert(p[offset] == ' ');
	}

	delete p;
}

- (BlockColor) getBlockColorToPlace:(int) count {
	BlockColor prev = EMPTY;
	if(count >= (int) [blockcolors_ count]) {
		// get last color, recursively filling in previous colors if necessary
		if(count > 0) { prev = [self getBlockColorToPlace:(count-1)]; }
		[blockcolors_ addObject:[NSNumber numberWithInt:randomColor(prev)]];
	}

	return (BlockColor) [[blockcolors_ objectAtIndex:count] intValue];
}

- (bool) getBlockDoublenessToPlace:(int) count {
	if(count >= (int) [blockdoubleness_ count]) {
		// recursively fill in previous doubleness if necessary
		if(count > 0) [self getBlockDoublenessToPlace:(count-1)];
		[blockdoubleness_ addObject:[NSNumber numberWithBool:randomDoubleness()]];
	}

	return [[blockdoubleness_ objectAtIndex:count] boolValue];
}

@end

